package com.gl.basis.common.handler;

import org.apache.shiro.authz.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;

import com.gl.basis.common.constant.IBaseConstants;
import com.gl.basis.common.constant.ITransCodeConsts;
import com.gl.basis.common.context.ContextUtils;
import com.gl.basis.common.dataenum.DatabaseExceptionEnum;
import com.gl.basis.common.exception.AccessException;
import com.gl.basis.common.exception.BaseException;
import com.gl.basis.common.exception.ExceptionResult;
import com.gl.basis.common.util.Base64Util;
import com.gl.basis.common.util.JSONUtil;
import com.gl.basis.common.util.StringUtils;
import com.netflix.hystrix.exception.HystrixRuntimeException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.websocket.DecodeException;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * MVC全局异常处理器
 *
 */
public class GlobalExceptionHandler extends ExceptionHandlerExceptionResolver {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @Override
    protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
                                                           HttpServletResponse response,
                                                           HandlerMethod handlerMethod,
                                                           Exception exception) {
        //打印异常信息
        printException(exception);

        //500错误HttpServletResponse.SC_INTERNAL_SERVER_ERROR  SC_OK
        response.setStatus(HttpServletResponse.SC_OK);

        ModelAndView mv = super.doResolveHandlerMethodException(request, response, handlerMethod, exception);
        if (handlerMethod == null) return mv;
        Class<?> beanType = handlerMethod.getBeanType();
        Method method = handlerMethod.getMethod();
        ResponseBody rbAn = AnnotationUtils.findAnnotation(method, ResponseBody.class);
        RestController rcAn = AnnotationUtils.findAnnotation(beanType, RestController.class);
        //请求类型:传统同步http请求还是ajax异步请求
        String reqtype = request.getHeader("X-Requested-With");
        //需要返回json错误信息的异常处理
        if ("XMLHttpRequest".equals(reqtype) || rbAn != null || rcAn != null) {
            mv = new ModelAndView(new MappingJackson2JsonView());
            //默认的异常信息
            boolean izSuccess = false;
            //获取AccessException
            Exception cause = causeAccessException(exception);
            if (cause != null) exception = cause;

            DatabaseExceptionEnum databaseExceptionEnum = null;
            // 自定义异常
            if (exception instanceof BaseException) {
                if (exception instanceof AccessException) {
                    ExceptionResult er = null;
                    try {
                        er = JSONUtil.toBean(exception.getMessage(), ExceptionResult.class);
                        String excep = er.getException();
                        //判断是否是封装异常的处理
                        Class<?> clazz = Class.forName(excep);
                        if (!(BaseException.class.isAssignableFrom(clazz))) {
                            mv.getModel().put("causeType", clazz.getSimpleName());
                            mv.getModel().put("code", ITransCodeConsts.ERROR_CODE);
                            mv.getModel().put("exception", clazz.getName());
                            mv.getModel().put("msg", ITransCodeConsts.ERROR_MSG);
                            mv.getModel().put("success", false);
                        } else {
                            mv.getModel().put("causeType", er.getCauseType());
                            mv.getModel().put("code", er.getCode());
                            mv.getModel().put("exception", er.getException());
                            mv.getModel().put("msg", er.getMsg());
                            mv.getModel().put("success", false);
                        }
                    } catch (Exception e) {
                        mv.getModel().put("causeType", exception.getClass().getSimpleName());
                        mv.getModel().put("code", ITransCodeConsts.ERROR_CODE);
                        mv.getModel().put("exception", exception.getClass().getName());
                        mv.getModel().put("msg", ITransCodeConsts.ERROR_MSG);
                        mv.getModel().put("success", false);
                    }
                } else {
                    mv.getModel().put("causeType", ((BaseException) exception).getClass().getSimpleName());
                    mv.getModel().put("code", ((BaseException) exception).getCode());
                    mv.getModel().put("msg", ((BaseException) exception).getMessage());
                    mv.getModel().put("success", izSuccess);
                    mv.getModel().put("exception", ((BaseException) exception).getClass().getName());
                }
            } else if ((databaseExceptionEnum = DatabaseExceptionEnum.valueOf(exception)) != null) {
                mv.getModel().put("causeType", exception.getClass().getSimpleName());
                mv.getModel().put("code", ITransCodeConsts.ERROR_CODE);
                mv.getModel().put("msg", "数据库操作异常:" + databaseExceptionEnum.getMsg());
                mv.getModel().put("success", izSuccess);
                mv.getModel().put("exception", exception.getClass().getName());
            } else if(exception instanceof  UnauthorizedException){
                mv.getModel().put("causeType", IBaseConstants.AUTH_EXCEPTION);
                mv.getModel().put("code", ITransCodeConsts.USER_PERMISSION_ERROR_CODE);
                mv.getModel().put("msg", ITransCodeConsts.USER_PERMISSION_ERROR_MSG);
                mv.getModel().put("success", izSuccess);
                mv.getModel().put("exception", exception.getClass().getName());
            } else {
        	mv.getModel().put("causeType", exception.getClass().getSimpleName());
        	mv.getModel().put("code", ITransCodeConsts.ERROR_CODE);
        	mv.getModel().put("msg", ITransCodeConsts.ERROR_MSG);
        	mv.getModel().put("success", izSuccess);
        	mv.getModel().put("exception", exception.getClass().getName());
        }
            setErrorHeader(new HashMap<>(mv.getModel()), exception);
            return mv;
        }
        //需要返回自定义错误页面的异常处理
        //该处不实现 ,使用spring boot的默认处理
        setErrorHeader(new HashMap<>(mv.getModel()), exception);
        return mv;
    }

    private Exception causeAccessException(Exception exception) {
        if (exception instanceof HystrixRuntimeException) {
            Throwable cause = exception.getCause();
            if (cause != null && cause instanceof DecodeException) {
                cause = exception.getCause().getCause();
                if (cause != null && cause instanceof AccessException) {
                    return (AccessException) cause;
                }
            }
        }
        return null;
    }

    /**
     * 打印和记录异常信息
     *
     * @param exception
     */
    public void printException(Exception exception) {
        //打印当前异常
        StackTraceElement stackTraceElement = exception.getStackTrace()[0];
        String className = stackTraceElement.getClassName();
        String methodName = stackTraceElement.getMethodName();
        String fileName = stackTraceElement.getFileName();
        int lineNumber = stackTraceElement.getLineNumber();
        logger.error("\n文件:{};行号:{};方法:{};发生异常:{}", fileName, lineNumber, className + "." + methodName, exception.getMessage());
        //打印cause异常
        Exception cause = causeAccessException(exception);
        if (cause != null) {
            logger.error("\n" + cause.getMessage());
        }
        logger.error("", exception); //打印异常
       
    }

    /**
     * 设置异常信息头,
     *
     * @param map
     * @param exception
     */
    public void setErrorHeader(Map<String, Object> map, Exception exception) {
        HttpServletResponse response = ContextUtils.getResponse();
        String header = response.getHeader(IBaseConstants.ERROR_HEADER);
        //将异常源头信息写出去
        if (StringUtils.isEmpty(header)) {
            if (exception instanceof AccessException) {
                response.setHeader(IBaseConstants.ERROR_HEADER, Base64Util.encodeStr(exception.getMessage()));
            } else {
                StackTraceElement stackTraceElement = exception.getStackTrace()[0];
                String className = stackTraceElement.getClassName();
                String methodName = stackTraceElement.getMethodName();
                String fileName = stackTraceElement.getFileName();
                int lineNumber = stackTraceElement.getLineNumber();
                map.put("fileName", fileName);
                map.put("lineNumber", lineNumber);
                map.put("method", className + "." + methodName);
                response.setHeader(IBaseConstants.ERROR_HEADER, Base64Util.encodeStr(JSONUtil.toJson(map)));
            }
        }
    }
}
